/*
 *  Gregorian.cpp
 *  Calendar
 *
 *  Created by Fredrik Gustafsson on 9/30/10.
 *  Copyright 2010 Kungliga Tekniska Högskolan. All rights reserved.
 *
 */

#include "gregorian.h"

using namespace lab2;

Gregorian::Gregorian() {
	time_t temp = k_time(NULL);
	struct tm* tmtemp = gmtime(&temp);
	curr_year = 1900+tmtemp->tm_year;
	curr_month = tmtemp->tm_mon+1;
	curr_day = tmtemp->tm_mday;
}

Gregorian::Gregorian(int year,int month,int day) {
	assert_valid_date(year, month, day);
	curr_year = year;
	curr_month = month;
	curr_day = day;
}

Gregorian::Gregorian(const Date* d) {
	this->set_date_from_base(d->mod_julian_day());
}

Gregorian::Gregorian(const Date& d) {
	this->set_date_from_base(d.mod_julian_day());
}

const bool Gregorian::assert_valid_date(int year,int month,int day){
	//TODO: alert when we're out of year range
	if (month<1 || month>12) {
		throw std::out_of_range("Month out of range");
	}
	if (day<1 || day>days_of_month(month,year)) {
		throw std::out_of_range("Day out of range");
	}
	if (year<1858 || year>2558) {
		throw std::out_of_range("Year out of range");
	}
	return true;
}

const int Gregorian::year() const{
    return curr_year;
}


const int Gregorian::month() const{
    return curr_month;
}


const int Gregorian::day() const{
    return curr_day;
}

const int Gregorian::week_day() const{
    int century_offset = get_century_offset(curr_year);
	
	int year_offset = ((curr_year%100)+((int)(curr_year%100)/4))%7;
	if (leap_year(curr_year) && (curr_month <= 2)){
		--year_offset;
	}
	
	static int month_offsets[13] = {-1234,0,3,3,6,1,4,6,2,5,0,3,5};
	int month_offset = month_offsets[curr_month];
	
	int day_offset = curr_day%7;
	
	int weekday = (century_offset+year_offset+month_offset+day_offset)%7;
	
	while (weekday<=0) {
		weekday+=7;
	}
	
    return weekday;
}


const int Gregorian::days_per_week() const{
    return 7;
}


const int Gregorian::days_this_month() const{
    return days_of_month(curr_month, curr_year);
}


const int Gregorian::months_per_year() const{
    return 12;
}


const std::string Gregorian::week_day_name() const{
    switch (week_day()) {
        case 1:
            return "monday";
        case 2:
            return "tuesday";
        case 3:
            return "wednesday";
        case 4:
            return "thursday";
        case 5:
            return "friday";
        case 6:
            return "saturday";
        case 7:
            return "sunday";
	}
	return NULL;
}


const std::string Gregorian::month_name() const{
    switch(curr_month) {
		case 1:
			return "january";
		case 2:
			return "february";
		case 3:
			return "march";
		case 4:
			return "april";
		case 5:
			return "may";
		case 6:
			return "june";
		case 7:
			return "july";
		case 8:
			return "august";
		case 9:
			return "september";
		case 10:
			return "october";
		case 11:
			return "november";
		case 12:
			return "december";
	}
	return NULL;
}

const int Gregorian::days_of_month(int month, int year) const{
	switch (month) {
		case 1:        //Januari
			return 31;
		case 2:        //Februari
			return (leap_year(year) ? 29 : 28);
		case 3:        //Mars
			return 31;
		case 4:        //April
			return 30;
		case 5:        //Maj
			return 31;
		case 6:        //Juni
			return 30;
		case 7:        //Juli
			return 31;
		case 8:        //Augusti
			return 31;
		case 9:        //September
			return 30;
		case 10:    //Oktober
			return 31;
		case 11:    //November
			return 30;
		case 12:    //December
			return 31;
		default:
			return 0;
	}
}

Date& Gregorian::add_year(const int years_to_add){
	curr_year += years_to_add;
	if (curr_month == 2 && curr_day == 29 && !leap_year(curr_year)) {
		curr_day = 28;
	}
	return *this;
}

Date& Gregorian::add_year(){
	this->add_year(1);
	return *this;
}

Date& Gregorian::add_month() {
	int old_month = curr_month, old_year = curr_year;
	if(++curr_month>12) {
		curr_month = 1;
		curr_year++;
	}
	if (curr_day > days_this_month()) {
		curr_year = old_year;
		curr_month = old_month;
		(*this)+=30;
	}
	return *this;
}

Date& Gregorian::sub_month() {
	int old_month = curr_month, old_year = curr_year;
	if(--curr_month<1) {
		curr_month = 12;
		--curr_year;
	}
	if (curr_day > days_this_month()) {
		curr_year = old_year;
		curr_month = old_month;
		(*this)-=30;
	}
	return *this;
}

Date& Gregorian::add_month(const int n){
	if (n<0)
		for (int i=n; i<0; ++i) {
			sub_month();
		}
	else
		for (int i=0; i<n; ++i) {
			add_month();
		}
	return *this;
}

void Gregorian::set_date_from_base(int jd) {
	
	double JMJD  = 2400000.5;
	
	double Z;
    const double F = modf(jd + JMJD + 0.5, &Z);

    int alpha = int((Z - 1867216.25) / 36524.25);
    int A = int(Z + 1 + alpha - alpha / 4);
	
    const int B = A + 1524;
    const int C = int((B - 122.1) / 365.25);
    const int D = int(365.25 * C);
    const int E = int((B - D) / 30.6001);
    curr_day = B - D - int(30.6001 * E) + F;
    if (E < 14) 
        curr_month = E - 1;
    else
        curr_month = E - 13;
    if (curr_month > 2)
        curr_year = C - 4716;
    else
        curr_year = C - 4715;
}


const int Gregorian::mod_julian_day() const {
	int yr = curr_year, mo = curr_month, day = curr_day;
	double JMJD  = 2400000.5;
	
	if (mo <= 2) {
        yr--;
        mo += 12;
	}
    const int A = yr / 100;
    int B = 2 - A + (A / 4);
    return int(365.25 * (yr + 4716)) + int(30.6001 * (mo + 1)) + day + B - 1524.5 - JMJD;
}

void Gregorian::print(std::ostream& os) const {
	os << this->curr_year << "-";
	if (this->curr_month <= 9) {
		os << "0";
	}
	os << curr_month << "-";
	if (curr_day <= 9) {
		os << "0";
	}
	os << curr_day;
}

bool Gregorian::leap_year(int year) const {
	return ((year&3) == 0 && (year%100 != 0 || year % 400 == 0));
}

const int Gregorian::get_century_offset(int year) const {
	int century = (int)year / 100;
	return ((39-century)%4)*2;
}

